Gemini Proxy
bbs.geminispace.org bbs.geminispace.org/s/C_Programming/46625
how do i return struct from a function or something like this in C? sorry for bad english.
Aug 02 · 11 days ago · 👍 gradmna
41 Comments ↓
Yes you can return structs from a function, by value.
Usually it is better to pass the function a pointer to a struct and have it fill in the values instead. This avoids extra allocation and copying.
The idiomatic way (the way most people do it) for object creation and manipulation in C is to allocate the memory for the object in your current function and pass a pointer in to the function doing the creation/manipulation. The return value is often a status or some other bit of information passing.
This does two things. It makes the memory management more recognizable as you're allocing up front. It also means that you can choose where the memory is being allocated from.
Although there are times it makes sense, especially if the struct is small, like struct sPoint {short x; short y;};
The compiler should optimize that into a register. And even if it doesn't, copying 2 shorts may be worth it for the clarity of code.
When you return a struct, the caller reserves space on the stack and passes that to the callee. So there is no actual copying happen, no matter the size of the struct. Really small structs are even returned (and passed) in registers to avoid even the stack allocation and memory access.
Then there is the Arch and ABI question, are you looking for efficiency, want your C to look like what happens or ok with compiler modifications, Is data copies ok or is it too much to copy to be slow.
Any struct can be returned by value but it depends on the CPU and OS if it will all be via registers or if the compiler modifies to be pass by reference.) Most of this stuff only becomes relevant if you want to become a system programmer. For most people its not relevant.
@js, since the function returns a struct that is anonymous, chances are that you will then assign it to some named lvalue, which is going to involve copying.
@stack I think you are confused. An anonymous struct is something else that we haven't talked about here. Assigning the struct returned by a function call to a variable does not create a copy due to copy elision. C doesn't explicitly spell this out since it doesn't matter for the correctness in C as it's merely an optimization for C, but C++ explicitly specifies it even because it matters for (con|de)structors.
I have views on this. In my C coding, the only function that returns a struct of type (say) Foo is defined as 'struct Foo *foo_new(...)'. It initializes the memory, fills in the starting values of the struct, and returns a pointer to it. The only function that tidies up the structure and frees the memory is defined 'void foo_delete (struct Foo *self)'.
While there are more efficient ways to handle this situation, I find that the consistency my approach imposes makes it much easier for me to keep control of the code in the long term. It's particularly relevant when structs contains as their members pointers to other structs (which will all have been created the same way).
Just my $0.02.
@js you're correct that "anonymous" is not the correct term there. It's also the case that in some situations, the assignment may avoid causing copying. But it's not true in general.
gcc, even with -O0, does indeed avoid a copy if you assign the result value (allocated on the stack) to another stack-allocated variable. But it can hardly avoid the copy (and therefore doesn't) if instead you copy it to a file-scope global, a static object, or a dynamically-allocated object like the following:
struct my_struct_t *s = malloc(sizeof *s);
*s = return_my_struct();
@shizukado Good point about the globals or malloced structs. In this case passing a pointer would indeed avoid the copy.
@js, you are correct that I am abusing the term. But to be fair, in this case I mean it literally--there is no symbolic name given to the returned struct-value. And yes the copying should be optimized away by a reasonaly decent compiler.
you can either do it by returning a value of the struct or a pointer to the struct, here's how you can do it.
suppose
struct MyStruct
is the struct type, and
var
is a variable holding a struct.
returning a raw value is the easier one, just have you function typed as
struct MyStruct func(...)
and the return statement will be smth like
return var;
however the pointer to a struct is a little more complex.
the function declaration will be:
struct MyStruct*func(...)
and the end of your function will be something like:
struct MyStruct*ptr = malloc(sizeof(struct MyStruct));
memcpy(ptr, &var, sizeof(struct MyStruct));
return ptr;
and for the function that uses your function, NEVER forget to free the return value!
hope this helps. :)
@gradmna That is even worse than returning by value since now you need to allocate on the heap and the caller needs to free. The much better way is to pass a struct in by reference that the function then sets. This allows avoiding a copy no matter where the struct should live in the end.
@js youre right, but ppl return structs in various ways so there isnt one "objectively best way" to return smth like a struct, i wanted to show op the diversity of their question ;p
@js : "The much better way is to pass a struct in by reference that the function then sets. This allows avoiding a copy..."
I think this was the idiomatic way of handling the situation for many years. Many functions in the standard C library work this way. I suspect it started to fall out of favour with the rise of object-oriented languages, and structs seen increasingly as classes.
In the end, if you're working on large, long-lived projects, I hold strongly to the view that code readability and expressiveness are nearly always more important than efficiency. Whatever method you pick should ideally be self-documenting.
Of course, there are many exceptions.
Allocation is really a separate issue.
You can be returning a pointer to an already-existing structure for instance.
When passing a pointer around, the struct may be mutated by many and has a bigger scope. Passing by value has a scope of just that expression, unless you store the value somewhere.
@stack : "When passing a pointer around, the struct may be mutated by many..."
Sure. But that's why we have 'const *', no?
I argued for years to have a similar feature added to Java, but got nowhere.
The const syntax in C is one of my least favorite features. Is the pointer const or is it the data? Do I mean volatile? And I usually don't like to prevent myself from mutating data - that is what God put me on this earth for...
@stack It’s pretty easy:
const char *foo
It points to a const char. The pointer can be modified, what it points to not because its const.
char *const foo
The pointer is const, but what it points to is a char.
So: Before the star is what it points to, after is about the pointer.
@stack : "Is the pointer const or is it the data?"
It's certainly possible to get hung up on this but I find that, in practical scenarios, it isn't a problem.
If I declare this function:
void foo (const int *x)
Then I know that function foo() will not modify the value of the thing its argument points to. At least, not without some ugly stuff. foo() can modify the value of the _pointer_ x, and make it point elsewhere, but that doesn't affect the caller of the function. For the caller, the data pointed to is untouchable.
There are many other simple, idiomatic uses of "const *" that hugely reduce the scope for error, in my experience.
Yes, it's not complicated but I invariably get it wrong the first time for some reason.
Also, I have yet to encounter a situation where I was happy with using const. I can think of several where I had to go through code removing constness.
The easiest way to deal with 'const' and pointers is to read them right to left.
int main() {
int x = 1;
int y = 2;
// p is a pointer to an int that is const
const int *p = &x;
*p = 3; // fails
p = &y;
// q is a const pointer of an int
int *const q = &x;
*q = 3;
q = &y; // fails
return 0;
}
@stack : "I can think of several where I had to go through code removing constness."
The only good reason I can think of for doing this, is that you have to incorporate a library supplied by somebody who didn't understand how const works.
The typical problem is that you have a library function that wrongly takes a non-const parameter, and you have to call it from a function, where the parameters are correctly const.
If you remove the const from your own function, then you'll probably find that some other compilation fails, because something else isn't const that should be. And so it goes on, until you've stripped all the const declarations and destroyed a major code quality feature.
The only good reason I can think of for doing this, is that you have to incorporate a library supplied by somebody who didn't understand how const works.
I would argue (and have argued)
that the 'free' C standard library
For those wondering why you would want to free a pointer to a const object, think of a shared read-only string—there may be any number of references to it, and it can be reference-counted so you can free it when it's no longer used by anything. But because of this flaw in the standard library, you have to strip the constness:
// const char *s;
free((/* non-const */ char *)s);
(The comment inside the cast explains the reason for the cast.)
@Christopher const means that you are not going to modify it. free() *does* modify it: It changes it from a defined value to an undefined value. So it very much mutates it and must not be const. That free isn't const is not an oversight at all – this is extremely intentional.
@js, while I totally understand what you are saying, I would argue that free renders a pointer to it completely invalid and dangling and discussion of whatever it is pointing to being const or not -- entirely moot
I would argue that having free() defined as it is makes it easier for the compiler to prevent us from making mistakes like this:
const char *message = get_error_text(...);
...
free (message)
I've defined get_error_text() to return a const * because I know that the string it returns is not dynamically allocated, so should not be free'd.
While I understand the point that @Christopher makes, on balance I think it's better that free() is defined as it is.
I avoid dynamic allocation like the plague, and it is often possible to do so. In my projects anyway, which tend to be very contained.
@stack : "I avoid dynamic allocation like the plague"
I'd agree that it's wise to avoid it where possible, both from code quality and efficiency perspectives.
But how would you implement, say, a variable-length list of text strings of variable size, without some measure of dynamic allocation? One strategy is to make everything over-sized and over-long, and modern computers have enough memory that this is often practicable. But I started programming in the 70s, and that kind of thing sticks in my throat.
In any case, the sad fact is that if you're working with data structures like trees and graphs, it may be impossible without dynamic allocation.
But prove me wrong :)
I've been working a lot with strings, and there are a couple of methods I use a lot.
The best way is elimination. For compilers, parsers and constrained user input (like in Spellbinding), I hash strings with FNV1a and throw them away. It is _way_ easier dealing with fixed-size tokens.
For situation where I need to aggregate variable-sized text, I try to use log strucures, preallocated. I then flush them at session end, or in more complicated situations, build specialized compactors and garbage collectors, some even fixing up pointers in code. By then I am far from C though.
I've defined get_error_text() to return a const * because I know that the string it returns is not dynamically allocated, so should not be free'd.
The problem is that const doesn't mean a pointer is not dynamically allocated; there actually is no type modifier that means that. And the problem can still happen if a function returns a non-const pointer to statically allocated memory. In either case it's a user problem (i.e., a user of the function). For example, 'gmtime' returns 'struct tm *'. It's not dynamically allocated either and shouldn't be freed by the caller.
In my personal experience, the few times I got tangled up in situations in which it was difficult to figure out when it is safe to free objects, it was entirely due to not thinking enough during the design phase.
Const is a pretty crude tool, and even sophisticated GC or borrow systems are no substitute for thinking (and frankly, just get in the way)
@Christopher : "The problem is that const doesn't mean a pointer is not dynamically allocated;..."
It does if I say it does :) That is, in my programming practice, that's what it means. If I write a function that returns a non-const pointer, the caller knows to take responsibility for it. gmtime() is an unusual case; I assume it was defined as it is before it's archaic. In my own practice, I wrap functions like this in my own stubs, so I can follow consistent memory management semantics.
@stack : Sure, none of this is a subsitute for thinking. It's no substitute for proper documentation, either.
But some of the software I maintain is more than twenty years old. At my age, when I can't always remember what I had for breakfast, I need to use whatever tools a programming language offers, to reduce the memory burden.
Using the const-correctness features of C and C++, methodically and consistently, has saved me from stupid, dangerous errors more often than I can remember.
@lars_the_bear--I am there too, and understand the sentiment... Just can't say const has ever helped me, no doubt due to being a dufus.
@stack : "...no doubt due to being a dufus."
More likely, perhaps, because you didn't start your programmic life with C/C++. I didn't get it at first, then I had an "aha!" moment when I realized why it mattered.
I see the same thing with C++ programmers, who don't understand why Java's compile-time exception checking is such a powerful tool.
No programming language needs any of these features, but I take the view that anything that allows an error to be caught before even running the program has to be a good idea, even if it's inconvenient.
Ok, I will give it a shot next time.
gmtime() is an unusual case
It's not so unusual. I can think of other standard library functions, such as fopen(), that return a pointer to a non-const object which should not be freed by the application. Since the pointer is not const-qualified, your application _could_ try to free() it, but Bad Things are likely to happen. Also see genenv(), strerror(), strchr(), strstr(), strtok(), and setlocale(), among others.
@Christopher : Fair point, but fopen() returns a pointer that becomes the responsibility of the caller. You can't call free() on it, but you can call fclose().
strstr(), strtok(), etc., don't really count, because they don't allocate anything.
It's always bugged me that strerror() returns a non-const. I suppose it can be forgiven, because (I guess) it pre-dates proper const semantics. GNU libc has strerrordesc_np() which does return a const pointer.
But, broadly, I agree -- there are functions in the standard library that don't follow modern practices for handling const values, presumably for historical reasons.
strstr(), strtok(), etc., don't really count, because they don't allocate anything.
You're right, and that's exactly the point I'm making. They return pointers to non-const, non-dynamically allocated memory. If you assume that a pointer to non-const can be freed, you'll be hosed if you try to free one of those pointers. If you have any function that returns a pointer (either const or non-const) you need to check its documentation to know if you need to free it yourself. If you get it wrong, you'll either have a memory leak or a corrupted heap.
Also consider this code:
void foo()
{
const int x = 5;
// ...
}
When this function returns, the automatic variable x is deallocated despite it being const. Does the value of x change when it's deallocated? Nope, the object itself just doesn't exist anymore. Same goes for free()ing an object that was malloc()ed.
TL;DR: Non-const doesn't mean dynamically allocated, and const doesn't mean non-dynamically allocated. Using const to mean non-dynamically allocated is an unusual convention and is definitely not idiomatic C.
Edit: here's a good read about const and why the Linux kfree() function takes a const pointer:
https://yarchive.net/comp/const.html
Another edit (sorry): constness and lifetime are distinct properties of an object. As in my example code above, a const object can be deallocated (its lifetime ends). So it would make sense for free() to take a const pointer, as free() doesn't modify the object—it only affects its lifetime.
you're on bbs.geminispace.org/s/C_Programming/46625